﻿#pragma once

#include "common.h"
#include <vector>

namespace anip
{
	class Shape : public GLObject
	{
	public:
		enum class Type
		{
			Points = 0x0000, // GL_POINTS,
			LineLoop = 0x0002, // GL_LINE_LOOP,
			LineStrip = 0x0003, // GL_LINE_STRIP,
			TriangleStrip = 0x0005, //GL_TRIANGLE_STRIP,
			TriangleFan = 0x0006, //GL_TRIANGLE_FAN
		};

		enum class StorageHint
		{
			Stream = 0x88e0, // GL_STREAM_DRAW,
			Static = 0x88e4, // GL_STATIC_DRAW,
			Dynamic = 0x88e8, // GL_DYNAMIC_DRAW
		};

		class VertexData : private GLObject
		{
			friend class Shape;

		private:
			unsigned int _vbo = 0;
			int _size = 0, _dim = -1;
			StorageHint _storage = StorageHint::Static;

			unsigned int id() const { return _vbo; }

			// ANIP_TODO: what??
			void finalize() { throw std::logic_error("Should not call VertexData::finalize"); }

			VertexData() {}

		public:
			VertexData(const VertexData& obj) = delete;

			int getDimension() const { return _dim; }
			int getSize() const { return _size; }
			StorageHint getStorage() const { return _storage; }

			void set(int n, const float* data, StorageHint storage = StorageHint::Static);
			void setPartial(int offset, int n, const float* data);
		};

	private:
		std::vector<VertexData*> _data;
		unsigned int _vao;

	public:
		Type type;

		Shape(Type t);
		Shape(Type t, const float* points, int n, 
			int dimension = 2, StorageHint storage = StorageHint::Static);

		Shape(const Shape& obj) = delete;

		unsigned int id() const { return _vao; }
		void finalize();

		int size() const { return _data.size() == 0 ? 0 : _data[0]->_size; }

		void use();
		void draw();

		/// <param name="dimension"> = number of float values per vertex in the data; if the data
		/// will contain multiple attributes packed together, use the total number here!</param>
		/// <returns>Reference to the newly added data entry</returns>
		VertexData& addDataSlot(int dimension);

		/// <param name="n"> = size (vertex count) of the data</param>
		/// <param name="dimension"> = number of float values per vertex in the data; if the data
		/// will contain multiple attributes packed together, use the total number here!</param>
		/// <param name="data"> = pointer to actual data</param>
		/// <param name="storage"> = a hint that affects how the data is stored in the GPU</param>
		/// <returns>Reference to the newly added data entry</returns>
		VertexData& addDataSlot(
			int n, int dimension, const float* data, 
			StorageHint storage = StorageHint::Static);

		/// <param name="i"> = index of the data entry to get</param>
		/// <returns>Reference to the data entry in question</returns>
		inline VertexData& getData(int i)
		{
			assert(i >= 0 && i < (int)_data.size());
			return *_data[i];
		}

		/// <param name="iData"> = index of the data entry to be assigned to the attribute</param>
		/// <param name="iAttrib"> = index of the attribute declared in the shader; you can also get it by 
		/// calling <c>Shader::getAttribLocation</c></param>
		/// <param name="dimension"> = actual dimension of the attribute</param>
		/// <param name="stride"> = offset, in number of values (not bytes), between consecutive vertex 
		/// attribute values</param>
		/// <param name="offset"> = offset before the first value</param>
		void bindData(int iData, int iAttrib, 
			int dimension = -1, int stride = 0, int offset = 0);
	};
}